/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Observable;
import org.apache.log4j.Logger;


/**
 *
 * @author Sakis Vtdk
 */
public class DatabaseTable extends Observable implements Serializable, Cloneable
{
    public DatabaseTable()
    {
        this._id = 0;
        this._collation = "";
        this._name = "";
        this._storageEngine = "";
		this._primaryKeyId = -1;

		this.addObserver(new ChangesObserver());
    }

	public DatabaseTable(String tableName) throws IllegalArgumentException
	{
		if(tableName == null)
		{
			throw new IllegalArgumentException("Cannot create table 'null'");
		}
		else
		{
			this._name = tableName;
			this._primaryKeyId = -1;

			this.addObserver(new ChangesObserver());
		}
	}

	@Override
	public DatabaseTable clone() throws CloneNotSupportedException
	{
		DatabaseTable dbTable = (DatabaseTable) super.clone();

		dbTable._columns = new ArrayList<TableColumn>(_columns.size());

		for(TableColumn tblCol : _columns)
		{
			dbTable._columns.add(tblCol.clone());
		}

		return dbTable;
	}

	/**
	 * Method for adding a new column to this table
	 * 
	 * @param columnName The name of the column
	 * @throws Exception If the name already exists an exception is thrown
	 */
	public void addColumn(TableColumn newColumn) throws Exception
	{
		String columnName = newColumn.getName();
		if(columnExists(columnName))
		{
			throw new Exception ("Column name \""+ columnName +"\" already exists in the current table!"); 
		}
		else
		{
			//we set the id for the column and we add it
			int nextId = this.getNextColumnId();
			newColumn.setId(nextId);
			newColumn.setParentId(this._id);
			
			if(this._columns.add(newColumn))
			{
				//Summary of the change
				String summary = "Column '"+ columnName +"' created";

				//create new change object
				Change change = new Change();
				change.setParentId(this._id);
				change.setId(nextId);
				change.setType(Change.Type.CREATE);
				change.setObjectChanged(Change.ObjectChanged.COLUMN);
				change.setChangeSummary(summary);
				change.setName(columnName);
				change.setParentName(this._name);
				
				//notify the observers
				setChanged();
				notifyObservers(change);

				//create log entry
				log.info(summary);
			}

		}
	}

	/**
	 * Method for renaming an existing column of this table
	 * 
	 * @param columnId The id of the column (based on the arrayList)
	 * @param columnName The new name of the column
	 * @throws Exception If the name already exists an exception is thrown
	 * @throws ArrayIndexOutOfBoundsException If the given columnId is incorrect (based on the arrayList)
	 */
	public void renameColumn(int columnId, String columnName) throws Exception, ArrayIndexOutOfBoundsException
	{
		if(columnExists(columnName))
		{
			throw new Exception ("Column name \""+ columnName +"\" already exists in the current table!"); 
		}
		else
		{
			//check the columnId
			if(columnId < 0 || columnId > this._columns.size()-1)
			{
				throw new ArrayIndexOutOfBoundsException("Column Index '"+ columnId +"' out of bounds!");
			}
			else
			{
				String oldName = this._columns.get(columnId).getName();
				this._columns.get(columnId).setName(columnName);

				String summary = "Column id: "+ columnId +" renamed from '"+ oldName +"' to '"+ columnName +"'";
				
				Change change = new Change();
				change.setParentId(this._id);
				change.setId(columnId);
				change.setType(Change.Type.UPDATE);
				change.setWhatChanged(Change.WhatChanged.NAME);
				change.setObjectChanged(Change.ObjectChanged.COLUMN);
				change.setOldValue(oldName);
				change.setNewValue(columnName);
				change.setChangeSummary(summary);
				change.setParentName(this._name);
				change.setName(columnName);
				
				setChanged();
				notifyObservers(change);
				
				log.info(summary);
			}
		}

	}

	/**
	 * Public Method returns true if a column with the given name already exists in the table
	 * @param columnName
	 * @return true or false based on whether the column exists
	 */
	public boolean columnExists(String columnName)
	{
		boolean found = false;
		for(TableColumn tmpCol : this._columns)
		{
			if(tmpCol.getName().equalsIgnoreCase(columnName))
			{
				found = true;
				break;
			}
		}
		return found;
	}

	/**
	 * Alters the DataType of the column with the given id
	 * 
	 * @param columnId The id of the column to change the DataType
	 * @param newDataType The new DataType
	 */
	public void changeColumnDataType(int columnId, TableColumn.DataType newDataType)
	{
		TableColumn column = this.getColumn(columnId);

		//TODO: Do some checking before changing the data type(?)

		String oldDataType = column.getDataType(true);
		column.setDataType(newDataType);

		//summary of the change
		String summary = "Type of column '"+ column.getName() +"' changed from '"+ oldDataType +"' to '"+ newDataType.toString() +"'";

		//create the change:
		Change change = new Change();
		change.setParentId(this._id);
		change.setParentName(this._name);
		change.setId(columnId);
		change.setType(Change.Type.UPDATE);
		change.setWhatChanged(Change.WhatChanged.DATA_TYPE);
		change.setObjectChanged(Change.ObjectChanged.COLUMN);
		change.setOldValue(oldDataType);
		change.setNewValue(column.getDataType(true));
		change.setChangeSummary(summary);
		change.setName(column.getName());

		//notify the observers
		setChanged();
		notifyObservers(change);

		log.info(summary);
	}

	/**
	 * Changes the PRIMARY_KEY flag of the given column
	 * 
	 * @param columnId The id of the column to set whether or not is primary key
	 * @param isPrimaryKey What the PRIMARY_KEY flag should be set to
	 */
	public void changeColumnPrimaryKey(int columnId, Boolean isPrimaryKey)
	{
		TableColumn column = this.getColumn(columnId);

		Boolean wasPrimaryKey = column.isPrimaryKey();
		column.setPrimaryKey(isPrimaryKey);

		//summary of the change
		String summary = "Column '"+ column.getName() +"' changed PRIMARY KEY to '"+ isPrimaryKey.toString() +"'.";

		//create the change:
		Change change = new Change();
		change.setParentId(this._id);
		change.setParentName(this._name);
		change.setId(columnId);
		change.setType(Change.Type.UPDATE);
		change.setWhatChanged(Change.WhatChanged.PRIMARY_KEY);
		change.setObjectChanged(Change.ObjectChanged.COLUMN);
		change.setOldValue(wasPrimaryKey.toString());
		change.setNewValue(isPrimaryKey.toString());
		change.setChangeSummary(summary);
		change.setName(column.getName());

		if(isPrimaryKey)
		{
			this._primaryKeyId = columnId;
		}
		else
		{
			this._primaryKeyId = -1;
		}

		//notify the observers
		setChanged();
		notifyObservers(change);

		log.info(summary);
	}

	/**
	 * Changes the AUTO_INCREMENT flag of the given column
	 * 
	 * @param columnId The id of the column to set whether or not is auto_increment
	 * @param isAutoIncrement What the AUTO_INCREMENT flag should be set to
	 */
	public void changeColumnAutoIncrement(int columnId, Boolean isAutoIncrement)
	{
		TableColumn column = this.getColumn(columnId);

		Boolean wasAutoIncrement = column.isAutoIncrement();
		column.setAutoIncrement(isAutoIncrement);

		//summary of the change
		String summary = "Column '"+ column.getName() +"' changed AUTO_INCREMENT to '"+ isAutoIncrement.toString() +"'.";

		//create the change:
		Change change = new Change();
		change.setParentId(this._id);
		change.setParentName(this._name);
		change.setId(columnId);
		change.setType(Change.Type.UPDATE);
		change.setWhatChanged(Change.WhatChanged.AUTO_INCREMENT);
		change.setObjectChanged(Change.ObjectChanged.COLUMN);
		change.setOldValue(wasAutoIncrement.toString());
		change.setNewValue(isAutoIncrement.toString());
		change.setChangeSummary(summary);
		change.setName(column.getName());

		//notify the observers
		setChanged();
		notifyObservers(change);

		log.info(summary);
	}

	/**
	 * Alters the DataSize of the column with the given id
	 * 
	 * @param columnId The id of the column to change the DataSize
	 * @param newDataType The new size for this type of data
	 */
	public void changeColumnDataTypeSize(int columnId, String newSize)
	{
		TableColumn column = this.getColumn(columnId);

		Integer oldSize = column.getSize();
		column.setSize(Integer.parseInt(newSize));

		//summary of the change
		String summary = "Data size of column '"+ column.getName() +"' changed from '"+ oldSize +"' to '"+ newSize +"'";

		//create the change:
		Change change = new Change();
		change.setParentId(this._id);
		change.setParentName(this._name);
		change.setId(columnId);
		change.setType(Change.Type.UPDATE);
		change.setWhatChanged(Change.WhatChanged.DATA_SIZE);
		change.setObjectChanged(Change.ObjectChanged.COLUMN);
		change.setOldValue(oldSize.toString());
		change.setNewValue(newSize);
		change.setChangeSummary(summary);
		change.setName(column.getName());

		//notify the observers
		setChanged();
		notifyObservers(change);

		log.info(summary);
	}

	/**
	 * Changes the default value of the column with the given id
	 * 
	 * @param columnId The id of the column to change the default value
	 * @param newDefaultValue The new default value of this column
	 */
	public void changeColumnDefaultValue(int columnId, String newDefaultValue)
	{
		TableColumn column = this.getColumn(columnId);

		String oldDefaultValue = column.getDefaultValue();
		column.setDefaultValue(newDefaultValue);

		//summary of the change
		String summary = "Default value of column '"+ column.getName() +"' changed from '"+ oldDefaultValue +"' to '"+ newDefaultValue +"'";

		//create the change:
		Change change = new Change();
		change.setParentId(this._id);
		change.setParentName(this._name);
		change.setId(columnId);
		change.setType(Change.Type.UPDATE);
		change.setWhatChanged(Change.WhatChanged.DEFAULT_VALUE);
		change.setObjectChanged(Change.ObjectChanged.COLUMN);
		change.setOldValue(oldDefaultValue);
		change.setNewValue(newDefaultValue);
		change.setChangeSummary(summary);
		change.setName(column.getName());

		//notify the observers
		setChanged();
		notifyObservers(change);

		log.info(summary);
	}

	// <editor-fold desc="Getters">

	public String getCollation() 
	{
		return _collation;
	}

	public int getId() 
	{
		return _id;
	}

	public String getName() 
	{
		return _name;
	}

	public String getStorageEngine() 
	{
		return _storageEngine;
	}

	public TableColumn getColumn(int i)
	{
		return this._columns.get(i);
	}

	public int getPrimaryKeyId()
	{
		return this._primaryKeyId;
	}

	public ArrayList<TableColumn> getColumns()
	{
		return this._columns;
	}

	public Boolean hasPrimaryKey()
	{
		Boolean hasPrimaryKey = false;

		if(this._primaryKeyId >= 0)
		{
			hasPrimaryKey = true;
		}

		return hasPrimaryKey;
	}

	private int getNextColumnId()
	{
		return this._columns.size();
	}

	// </editor-fold>

	// <editor-fold desc="Setters">

	public void setPrimaryKeyId(int primaryKeyId)
	{
		this._primaryKeyId = primaryKeyId;
	}
	
	public void setCollation(String _collation) 
	{
		this._collation = _collation;
	}

	public void setId(int _id) 
	{
		this._id = _id;
	}

	public void setName(String _name) 
	{
		this._name = _name;
	}

	public void setStorageEngine(String _storageEngine) 
	{
		this._storageEngine = _storageEngine;
	}

    // </editor-fold>
    
	// <editor-fold desc="Private Variables">

	/**
	 * The id of the table
	 */
    private int _id;

	/**
	 * The name of the table
	 */
    private String _name;

	/**
	 * A collection of the columns contained in this table
	 */
    private ArrayList<TableColumn> _columns = new ArrayList<TableColumn>();

	/**
	 * The collation of the table
	 */
    private String _collation;

	/**
	 * The storage engine of the table
	 */
    private String _storageEngine;

	/**
	 * The column id of the primary key
	 */
	private int _primaryKeyId;

	/**
	 * Logger instance
	 */
	private static Logger log = Logger.getLogger(DatabaseTable.class);

	/**
	 * The class' id, used for serialization of the object
	 */
	private static final long serialVersionUID = 2L;

	// </editor-fold>
}
